package com.xucia.strands;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.mozilla.javascript.BaseFunction;
import org.mozilla.javascript.Callable;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.IdFunctionObject;
import org.mozilla.javascript.IdScriptableObject;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.SpawningContextFactory;

/**
 * Futures are used to suspend execution until they are fulfilled. Futures can
 * be containers for a single value that will be provided at a later time. This
 * is the central API for Rhino Strands coroutine support. Before using Futures in Rhino
 * JavaScript you must first call Future.init(scope). Normally to 
 * initialize coroutine support, you should call Future.init(scope) immediately after calling initStandardObjects
 * Context:
 * <pre>
  		scope = cx.initStandardObjects();
		Future.init(scope);
	</pre>   
 * To use a future, a future should be created that will represent a future
 * execution result. Programs can call Future.result() to then block/suspend
 * execution until the future is fulfilled. This allows for execution
 * suspension. When the future is fulfilled this triggers the resumption of
 * execution. The execution suspension and resumption uses threads for the
 * parrallel paths of execution, however these threads are completely
 * synchronized so that no two threads should ever be executing JavaScript at
 * the same time, effectively creating cooperative threads or fibers.
 * 
 * @author Kris Zyp
 */
public class Future extends IdScriptableObject implements
		java.util.concurrent.Future {
	private static final Object UNFULFILLED_RESULT = new Object();

	private static final Object FUTURE_TAG = new Object();
	private boolean isInterrupting;
	Object result = UNFULFILLED_RESULT;

	
	public String getClassName() {
		return "Future";
	}

	/**
	 * Creates a new Future. It will not be fulfilled until fulfill is called.
	 * 
	 */
	public Future() {
		createNotifier();
	}

	SpawnedFiber spawnedFiber;

	/**
	 * Creates a new Future that will be fulfilled by the execution (with the
	 * return value) of the provided function call. The function will be
	 * executed as a new thread/fiber. If the called function or any of the
	 * calls that it makes, suspends execution, (by calling future.result() on a
	 * future that has not been fulfilled), the constructor function will return
	 * without waiting for execution to be resumed. If the called function(s) do
	 * not suspend, it will return when finished, returning a fulfilled future
	 * object. This behaves the same as calling (except that it is executed as a
	 * new thread):
	 * 
	 * <pre>
	 * func.apply(scopeThis, arguments);
	 * </pre>
	 * 
	 * @param context
	 * @param scope
	 * @param function -
	 *            the function to execute
	 * @param args -
	 *            The arguments to provide the executing function as an array.
	 * @param scope -
	 *            This the "this" for the calling function.
	 * @param callback -
	 *            If a callback is provided it will be executed on completion of
	 *            the function's execution
	 */
	public Future(final Context context, final Scriptable scope,
			final Callable function, final Object[] args,
			final Scriptable scopeThis, final Callable callback) {
		createNotifier();
		if (callback != null)
			addListener(callback);
		this.spawnedFiber = new SpawnedFiber() {
			public void run() {
				SpawningContextFactory.shareContextWithCurrentFiber(context);
				try {
					Future.this.fulfill(context,scope,function.call(context, scope,
							scopeThis, args));
				} catch (Exception e) { // remove this catch and the debugger
										// will automatically stop at all
										// uncaught exceptions, sometimes it is
										// pretty nice
					e.printStackTrace();
				} finally {
					synchronized (this) {
						notify();
					}
				}
			}
		};
		synchronized (this.spawnedFiber) { 
			this.spawnedFiber.start();
				try {
					this.spawnedFiber.wait();// wait for the spawn lock to open so we can proceed if the execution is finished or suspended
				} catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
		}

	}

	/**
	 * This represents the function that should be called or fulfilled to
	 * fulfill the parent Future.
	 * 
	 * @author Kris Zyp
	 * 
	 */
	public class Notifier extends BaseFunction {
		/**
		 * Fulfill the parent Future
		 * 
		 * @param value
		 */
		public void fulfill(Context cx, Scriptable scope, Object value) {
			Future.this.fulfill(cx,scope,value);
		}

		
		public Object call(Context cx, Scriptable scope, Scriptable thisObj,
				Object[] args) {
			if (args.length > 0)
				fulfill(cx,scope,args[0]);
			else
				fulfill(cx,scope,Scriptable.NOT_FOUND);
			return Scriptable.NOT_FOUND;
		}

		int activeNotifiers = 0;

		/**
		 * The returns a disposable notifier. A disposable notifier is a
		 * notifier such that when and if all disposable notifiers for a
		 * Future/Notifier are garbage collected and finalized, the Future will cancelled.
		 * This can be used in situations where Java code executes the notifiers to
		 * resume execution, but if the Java handle on the notifier becomes
		 * unreachable and garbage collected than the Future and it's waiting suspension should be
		 * abandoned
		 * 
		 * @return disposable notifier
		 */
		public Notifier getDisposableNotifier() {
			activeNotifiers++;
			return new Notifier() {
				
				public Notifier getDisposableNotifier() {
					return this;
				}

				
				public void finalize() {
					if (--Notifier.this.activeNotifiers == 0)
						cancel(false);
				}
			};
		}
	}

	/**
	 * The returns a disposable notifier. A disposable notifier is a
	 * notifier such that when and if all disposable notifiers for a
	 * Future/Notifier are garbage collected and finalized, the Future will cancelled.
	 * This can be used in situations where Java code executes the notifiers to
	 * resume execution, but if the Java handle on the notifier becomes
	 * unreachable and garbage collected than the Future and it's waiting suspension should be
	 * abandoned
	 * 
	 * @return disposable notifier
	 */
	public Notifier getDisposableNotifier() {
		return ((Notifier) get("fulfill", this)).getDisposableNotifier();
	}
	/**
	 * The notifier must be able to be called with the correct scope and know what future it is connected to.
	 */
	private void createNotifier() {
		put("fulfill", this, new Notifier());
	}

	/**
	 * Initializes the provided scope for Future/coroutine support
	 * @param scope
	 */
	public static void init(Scriptable scope) {
		init(scope,false);
	}
	/**
	 * Initializes the provided scope for Future/coroutine support
	 * @param scope
	 * @param sealed
	 */
	public static void init(Scriptable scope, boolean sealed) {
		if (ContextFactory.hasExplicitGlobal()) { 
			if (!(ContextFactory.getGlobal() instanceof SpawningContextFactory))
				throw new RuntimeException("There is an explicit context factory being used and coroutine support uses an explicit context factory to handle top calls, you may need to integrate SpawningContextFactory into your explicit contex factory");
		}
		else
			ContextFactory.initGlobal(new SpawningContextFactory());
		Future obj = new Future();
		// Set the value of the prototype Date to NaN ('invalid date');
		obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
	}

	/**
	 * Sets the value for this future and resumes any execution that is waiting
	 * on this future.
	 * @param value -
	 *            the value for this future.
	 */
	public void fulfill(Context cx, Scriptable scope, Object value) {
		result = value;
		List waitingFibers = this.waitingFibers;
		this.waitingFibers = null;
		if (waitingFibers != null) {
			for (int i = 0; i < waitingFibers.size(); i++)
			{
				Object fiber = waitingFibers.get(i);
				synchronized (fiber) {
					if (fiber instanceof SpawnedFiber) {
						fiber.notify();
						try {
							fiber.wait();
						} catch (InterruptedException e) {
							throw new RuntimeException(e);
						}
					}
					else if (fiber instanceof Callable) { // execute callable in it's own fiber so we don't block
						new Future(cx,scope,(Callable) fiber,new Object[] {},this,null);
					}
				}
			}
		}
	}

	public class SpawnedFiber extends Thread {
		public void cancel() {
			Future.this.cancel(false);
			stop();
		}

	}
	/** 
	 * Just calls result. Implemented so we can say we implemented java.util.concurrent.Future
	 */
	public Object get() throws InterruptedException, ExecutionException {
		try {
			return get(0, (TimeUnit) null);
		} catch (TimeoutException e) {
			throw new RuntimeException(e);
		}
	}

	/** 
	 * Just calls result. Implemented so we can say we implemented java.util.concurrent.Future
	 */
	public Object get(long timeout, TimeUnit unit) throws InterruptedException,
			ExecutionException, TimeoutException {
		return result(Context.enter(), timeout);
	}

	/**
	 * Returns the value for this future. If the future has not been fulfilled
	 * yet, it blocks (suspends execution) until the value is provided. If the
	 * future has been fulfilled, it immediately returns the future's value.
	 * 
	 * @param cx
	 * @param timeout -
	 *            <tt>timeout</tt> &mdash; (optional) the number of
	 *            milliseconds before Future.TimeoutException is thrown. If not
	 *            specified, <tt>result()</tt> will wait forever for the
	 *            future to be fulfilled.
	 * @return
	 */
	public Object result(Context cx, long timeout) {
		try {
			return js_result(this, cx, timeout);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/*
	 * private static class WaitPoint { SpawnedFiber spawnedFiber; }
	 */
	List waitingFibers = new ArrayList();

	private static Object js_result(Future thisObj, Context cx, long timeout)
			throws InterruptedException, ExecutionException, TimeoutException {
		if (thisObj.result == UNFULFILLED_RESULT) {
			SpawnedFiber spawnedFiber = null;
			if (Thread.currentThread() instanceof SpawnedFiber)
				spawnedFiber = (SpawnedFiber) Thread.currentThread();
			thisObj.waitingFibers.add(spawnedFiber);
			if (spawnedFiber != null) {
				synchronized (spawnedFiber) { // the spawn call can now go on
					spawnedFiber.notify();
					spawnedFiber.wait();
				}
			}
		}
		if (thisObj.isInterrupting)
			throw new JavaScriptException(thisObj.result);
		return thisObj.result;
	}

	/**
	 * Cancels the future and stops all the threads/fibers that are waiting on
	 * it
	 */
	public boolean cancel(boolean mayInterruptIfRunning) {
		if (waitingFibers != null)
			for (int i = 0; i < waitingFibers.size(); i++)
			{
				Object fiber = waitingFibers.get(i);
				if (fiber instanceof SpawnedFiber)
					((SpawnedFiber) fiber).cancel();
			}
		waitingFibers = null;
		return true;
	}
	private static void js_interrupt(Future thisObj, Object error) {
		thisObj.interrupt(error);
	}
	
	public void interrupt(Object error) {
		isInterrupting = true;
		result = error;
	}

	/**
	 * When the future is fulfilled (if it has not already been), the given
	 * callback function will be executed when the future is completed. If the
	 * future has already been completed, this function will no effect.
	 * 
	 * @param callback
	 */
	public void addListener(Callable callback) {
		if (waitingFibers != null)
			waitingFibers.add(callback);
	}

	public boolean isCancelled() {
		return true;
	}

	/**
	 * Returns true if the future has been fulfilled.
	 */
	public boolean isDone() {
		return js_isDone(this);
	}

	private static boolean js_isDone(Future thisObj) {
		return thisObj.result != UNFULFILLED_RESULT;
	}

	protected void initPrototypeId(int id) {
		String s;
		int arity;
		switch (id) {
		case Id_result:
			arity = 1;
			s = "result";
			break;
		case Id_isDone:
			arity = 0;
			s = "isDone";
			break;
		case Id_interrupt:
			arity = 1;
			s = "interrupt";
			break;
		case Id_addListener:
			arity = 1;
			s = "addListener";
			break;
		case Id_constructor:
			arity = 4;
			s = "constructor";
			break;
		default:
			throw new IllegalArgumentException(String.valueOf(id));
		}
		initPrototypeMethod(FUTURE_TAG, id, s, arity);
	}

	protected int findPrototypeId(String s) {
		if ("constructor".equals(s))
			return Id_constructor;
		if ("result".equals(s))
			return Id_result;
		if ("isDone".equals(s))
			return Id_isDone;
		if ("interrupt".equals(s))
			return Id_interrupt;
		if ("addListener".equals(s))
			return Id_addListener;
		return 0;
	}

	
	public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
			Scriptable thisObj, Object[] args) {

		try {
			if (!f.hasTag(FUTURE_TAG)) {
				return super.execIdCall(f, cx, scope, thisObj, args);
			}
			int id = f.methodId();
			switch (id) {
			case Id_constructor:
				if (args.length > 0) {
					Object[] newArgs;
					if (args.length > 1) {
						NativeArray argsArray = ((NativeArray) args[1]);
						newArgs = new Object[(int) argsArray.getLength()];
						for (int i = 0; i < argsArray.size(); i++)
							newArgs[i] = argsArray.get(i);
							
					} else
						newArgs = new Object[0];
					return new Future(cx, scope, (Callable) args[0], newArgs,
							args.length > 2 ? (Scriptable) args[2] : scope,
							args.length > 3 ? (Callable) args[3] : null);
				}
				return new Future();
			}
			if (!(thisObj instanceof Future))
				throw incompatibleCallError(f);
			switch (id) {
			case Id_result:
				return js_result((Future) thisObj, cx,args.length == 0 ?
						0 : ((Number) args[0]).longValue());
			case Id_isDone:
				return new Boolean(js_isDone((Future) thisObj));
			case Id_interrupt:
				js_interrupt((Future) thisObj, (Scriptable) args[0]);
				return Scriptable.NOT_FOUND;
			case Id_addListener:
				return Scriptable.NOT_FOUND;

			default:
				throw new IllegalArgumentException(String.valueOf(id));
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	/**
	 * This makes a call in a new thread/fiber. If the call finishes without suspending execution then the value is returned.
	 * If the call suspends execution, this will return a future that represents the value to be returned from the future execution.  
	 * @param callable
	 * @param cx
	 * @param scope
	 * @param thisObj
	 * @param args
	 * @return
	 */
	public static Object spawnIfNeeded(Callable callable, Context cx, Scriptable scope,
			Scriptable thisObj, Object[] args) {
		Future future = new Future(cx, scope, callable, args, thisObj, null);
		if (future.isDone())
			return future.result(cx, 0);
		else
			return future;		
	}

	private static final int Id_constructor = 1, Id_result = 2, Id_isDone = 3,
			Id_interrupt = 4, Id_addListener = 5, MAX_PROTOTYPE_ID = 5;
}
